package com.mloeppky.setgame.client;

import static com.google.common.collect.Lists.newArrayList;

import java.util.List;
import java.util.Set;
import java.util.Stack;

import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.user.client.ui.FlexTable;

/**
 * The board where all of the cards are laid out and selected.
 * @author mloeppky
 *
 */
public class SetBoard extends FlexTable {
	/**
	 * The cards that havent't been played in this game yet.
	 */
	private SetDeck deck;
	
	/**
	 * The cards that are on the board. Ordered from top left moving to bottom right.
	 */
	private List<SetCard> setCards;
	
	/**
	 * The class to send {@link FocusEvent}s to when a card is clicked.
	 */
	private FocusHandler handler;
	
	/**
	 * The number of columns wide the board is. Used to determine indexing for
	 * {@link SetBoard#setCards}.
	 */
	private int cols;

	
	/**
	 * @param rows The height of the board
	 * @param cols The width of the board
	 * @param handler The class to send {@link ClickEvents} to
	 * @throws EmptyDeckException If rows * cols is greater than 81
	 */
	private SetBoard(int rows, int cols, FocusHandler handler) throws EmptyDeckException {
		this.cols = cols;
		this.handler = handler;
		this.setCards = newArrayList();
		this.deck = new SetDeck(handler);
		
		//Fill the board with the given dimensions with cards from the deck
		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				setNewSetCard(r, c);
			}
		}
		
		//Give each card some padding. This could be moved to the css but currently
		//it is the only formatting of the board
		this.setCellSpacing(20);
	}
	
	/**
	 * Factory method
	 * @param rows The height of the board
	 * @param cols The width of the board
	 * @param handler The class to send {@link ClickEvents} to
	 * @return A new {@link SetBoard} object
	 * @throws IllegalArgumentException If rows * cols is greater than 81
	 */
	public static SetBoard newSetBoard(int rows, int cols, FocusHandler handler) {
		try {
			return new SetBoard(rows, cols, handler);
		} catch (EmptyDeckException e) {
			throw new IllegalArgumentException("You cant have a board with greater than 81 cards");
		}
	}
	
	/**
	 * Removes the given setCards from the board. Does not replace them with
	 * new cards.
	 * @param setCards The cards to remove
	 */
	public void remove(Set<SetCard> setCards) {
		for (SetCard sc : setCards) {
			remove(sc);
		}
	}
	
	/**
	 * Remove the given setCard from the board. Does not replace it with a new card.
	 * @param sc The card to remove
	 */
	public void remove(SetCard sc) {
		setCards.remove(sc);
		redraw();
	}
	
	/**
	 * Determines the number of sets that are currently on the board.
	 */
	public int numSets() {
		return numSets(0, new Stack<SetCard>());
	}
	
	/**
	 * Recursively implements {@link SetBoard#numSets()}
	 * @param startIndex 
	 * @param candidates
	 * @return
	 */
	private int numSets(int startIndex, Stack<SetCard> candidates) {
		if (candidates.size() == 3) {
			if (SetUtils.isSet(candidates)) {
				return 1;
			} else {
				return 0;
			}
		} else {
			int numSets = 0;
			for (int i = startIndex; i < setCards.size(); i++) {
				candidates.push(setCards.get(i));
				numSets += numSets(i+1, candidates);
				candidates.pop();
			}
			return numSets;
		}
	}
	
	/**
	 * Clears the table and re-adds each card. This is used to clean up the
	 * board after a set has been removed.
	 */
	private void redraw() {
		clear();
		for (int i = 0; i < setCards.size(); i++) {
			int rIndex = i / cols;
			int cIndex = i % cols;
			setWidget(rIndex, cIndex, setCards.get(i));
		}
	}
	
	public void addRow() throws EmptyDeckException {
		for (int c = 0; c < cols; c++) {
			setNewSetCard(setCards.size() / cols, c);
		}
	}
	
	private void setNewSetCard(int r, int c) throws EmptyDeckException {
		SetCard sc = deck.drawCard();
		setCards.add(sc);
		setWidget(r, c, sc);
	}


}
